/* shell.c, Ait, BSD 3-Clause, Kevin Bloom, 2023-2024 */

#include <stdio.h>
#include "header.h"
#include "termbox.h"
#include "util.h"

char opcmdtext[STRBUF_M];
char ipcmdtext[STRBUF_M];

/* TODO: make this generic
   M-e will display "Shell Command: " in the msgline. You then input the command
   you want.
   Eventually maybe make it so that there are different types of commands:
   - input, inputs something at the point
   - open, runs a command and ait will open the output (this currently works)
   - region/replace, use the region as the input to the shell cmd and then
     replace the region with the output
   - new buffer, runs the command and the output is placed in a new buffer

   I probably would want some keybinds to certain commands, however.
   Also, I'd like to make it so that if you have a region selected, it can be
   executed much like how acme does it.

   io = Insert = 0, Open = 1
*/
void get_popen_data(int io) {
  FILE *pf;
  char *command = NULL;
  char *data = NULL;
  buffer_t *bp;
  char *insertp = "Shell Command", *openp = "Open Via";
  char prompt[STRBUF_M + 12 + strlen(insertp)];
  int cpos = 0, done = FALSE;
  int c = 0, k = 0, hasregion = FALSE, cmdsize = 0, escaped_size = 0;
  int start_col = strlen(prompt), didtry, fd;
  int line = 0, onscrap = 0;
  int newlines = 0, oline = curbp->b_line;
  struct tb_event ev;
  char cmdtext[STRBUF_M], *cbuf; /* cbuf is the colon buffer for line number */
  cmdtext[0] = '\0';
  point_t point;
  char_t *oscrap = NULL, *escaped_region = NULL;
  const char* path = getenv("PATH");
  FILE *fp = NULL;
  char sys_command[CHUNK];
  static char temp_file[] = TEMPFILE;

  if(io) {
    sprintf(prompt, "%s", openp);
    if(opcmdtext[0] != '\0') {
      strcat(prompt, " (default ");
      strcat(prompt, opcmdtext);
      strcat(prompt, ")");
    }
  } else {
    sprintf(prompt, "%s", insertp);
    if(ipcmdtext[0] != '\0') {
      strcat(prompt, " (default ");
      strcat(prompt, ipcmdtext);
      strcat(prompt, ")");
    }
  }

  strcat(prompt, ": ");

  start_col = strlen(prompt);

  display_prompt_and_response(prompt, cmdtext);
  cpos = strlen(cmdtext);

  for (;;) {
    didtry = (k == 0x09);  /* Was last command tab-completion? */
    tb_present();
    if(execute_kbd_macro) {
      use_kbd_macro(&ev);
    } else if(tb_poll_event(&ev) != TB_OK) return;

    if(msgline_editor(ev, prompt, cmdtext, &cpos)) {
      continue;
    }

    if(!ev.mod)
      k = ev.ch;
    else
      k = ev.key;

    if(record_input) {
      record_buffer[record_buffer_index] = ev;
      record_buffer_index++;
    }

    /* ignore control keys other than return, C-g, backspace, CR,  C-s, C-R, ESC, tab */
      if (k < 32 &&
          k != TB_KEY_CTRL_G &&
          k != TB_KEY_BACKSPACE &&
          k != TB_KEY_BACKSPACE2 &&
          k != TB_KEY_ENTER &&
          k != TB_KEY_ESC &&
          k != TB_KEY_TAB)
      continue;

    switch(k) {
    case TB_KEY_ENTER: /* return */
      done = TRUE;
      break;

    case TB_KEY_ESC: /* esc */
    case TB_KEY_CTRL_G: /* ctrl-g */
      if (fp != NULL) fclose(fp);
      tb_set_cursor(0, MSGLINE);
      clrtoeol("", MSGLINE);
      return;

    case TB_KEY_BACKSPACE2: /* del, erase */
    case TB_KEY_BACKSPACE: /* backspace */
      if (cpos == 0)
        continue;
      cmdtext[--cpos] = '\0';
      tb_set_cursor(start_col + cpos, MSGLINE);
      display_prompt_and_response(prompt, cmdtext);
      break;

do_tab:
    case TB_KEY_TAB: {
      char curpath[PATH_MAX];
      char pcmd[PATH_MAX];
      int ii = 0;
      for(; !isspace(cmdtext[cpos]) && cpos > 0; cpos--)
        ;;
      for(int iii = 0; cmdtext[cpos+iii] != '\0'; iii++){
        if(!isspace(cmdtext[cpos+iii])) {
          pcmd[ii] = cmdtext[cpos+iii];
          ii++;
        }
      }
      if(cpos > 0)
        cpos++;
      pcmd[ii] = '\0';
      if(!didtry) {
        if (fp != NULL) fclose(fp);
        strcpy(temp_file, TEMPFILE);
        if (-1 == (fd = mkstemp(temp_file)))
          fatal("%s: Failed to create temp file\n");
        strcpy(sys_command, "printf \"%s\\n\" ");
        for(int i = 0, ii = 0; path[i] != '\0'; i++, ii++) {
          if(path[i] == ':') {
            curpath[ii] = '\0';
            strcat(sys_command, curpath);
            strcat(sys_command, "/");
            strcat(sys_command, pcmd);
            strcat(sys_command, "* ");
            ii = 0;
            i++;
          } else
            curpath[ii] = path[i];
        }
        strcat(sys_command, "| awk '$0 !~ \"\\\\*\" { split($0, a, \"/\"); print a[length(a)] }' | sort -u >");
        strcat(sys_command, temp_file);
//         strcat(sys_command, " 2>&1");
        (void) ! system(sys_command); /* stop compiler unused result warning */
        fp = fdopen(fd, "r");
        unlink(temp_file);
      }

      while ((c = getc(fp)) != EOF && c != '\n') {
        if (cpos < PATH_MAX - 1 && c != '*') {
          cmdtext[cpos++] = c;
          cmdtext[cpos] = '\0';
        }
      }

      cmdtext[cpos] = '\0';
      for(int i = cpos+1; cmdtext[i] != '\0'; i++)
        cmdtext[i] = 0;
      if (c != '\n' || c == -1) rewind(fp);
      if(c == -1)  goto do_tab;
      didtry = 1;
      tb_set_cursor(start_col, MSGLINE);
      clrtoeol(prompt, MSGLINE);
      addstr(cmdtext);
      break;
    }

    default:
      if (cpos < STRBUF_M - 1) {
        tb_set_cursor(start_col + cpos, MSGLINE);
        cmdtext[cpos++] = k;
        cmdtext[cpos] = '\0';
        addch(k);
      }
      break;
    }
    if(done)
      break;
  }

  if(cmdtext[0] == '\0') {
    if(io && opcmdtext[0] != '\0')
      strncpy(cmdtext, opcmdtext, STRBUF_M);
    else if(!io && ipcmdtext[0] != '\0')
      strncpy(cmdtext, ipcmdtext, STRBUF_M);
    else {
      clrtoeol("", MSGLINE);
      return;
    }
  }

  if(io)
    strncpy(opcmdtext, cmdtext, STRBUF_M);
  else
    strncpy(ipcmdtext, cmdtext, STRBUF_M);

  if (curbp->b_mark != NOMARK && curbp->b_point != curbp->b_mark) {
    oscrap = (char_t*) malloc(scrap.len);
    onscrap = scrap.len;
    (void) memcpy(oscrap, scrap.data, scrap.len * sizeof (char_t));
    copy_cut(TRUE, TRUE, FALSE);
    hasregion = TRUE;
  }

  strcpy(temp, editor_dir);

  tb_shutdown();

  if(hasregion) {
    /* Find all dollar signs and increase the size by one for each sign. */
    for(int i = 0; scrap.data[i] != '\0'; i++) {
      if(scrap.data[i] == '$' || scrap.data[i] == '`' || scrap.data[i] == '"')
        escaped_size += 2;
      else
        escaped_size++;
    }
    escaped_region = malloc(sizeof(char_t *)*escaped_size+1);
    /* Escape all $ with \$, ` with \`, and " with \". This prevents
       the echo command from trying to do a variable substitution,
       command execution, and removal of double quotes.
    */
    for(int i = 0, k = 0; scrap.data[i] != '\0'; i++, k++) {
      if(scrap.data[i] == '$' || scrap.data[i] == '`' || scrap.data[i] == '"') {
        escaped_region[k] = '\\';
        k++;
        escaped_region[k] = scrap.data[i];
      } else {
        escaped_region[k] = scrap.data[i];
      }
    }
    escaped_region[escaped_size] = '\0';
    cmdsize = 6*4*escaped_size*strlen(cmdtext);
    command = malloc(sizeof(char *)*cmdsize);
    strcpy(command, "echo \"");
    strcat(command, (char *)escaped_region);
    strcat(command, "\" | ");
    strcat(command, cmdtext);
  } else {
    cmdsize = strlen(cmdtext);
    command = malloc(sizeof(char *)*cmdsize);
    strcpy(command, cmdtext);
  }

  // Setup our pipe for reading and execute our command.
  pf = popen(command,"r");

  if(pf == NULL){
    msg("Could not open pipe for output.");
    return;
  }

  data = malloc(sizeof(char *) * 512 * 3);
  fgets(data, sizeof(char *) * 512 * 3, pf);

  tb_init();
  LINES = tb_height();
  COLS = tb_width();
  MSGLINE = LINES-1;
  tb_set_input_mode(TB_INPUT_ALT);

  /* Mark the log for update */
  redraw();

  /* check if canceled command */
  if(data[0] == -1 || data[0] == 0) {
    if(io == 0) {
      /* put the original contents back in the buffer and reset scrap */
      paste_internal(FALSE);
      free(scrap.data);
      scrap.len = onscrap;
      scrap.data = (char_t*) malloc(scrap.len);
      (void) memcpy(scrap.data, oscrap, scrap.len * sizeof (char_t));
    }
  } else {
    switch(io) {
      case 0: {
        for(int z = 0; data[z] != '\0'; z++) {
          input[0] = (char_t)data[z];
          input[1] = '\0';
          undoset(INSERT, z != 0);
          insert();
          if(input[0] == '\n')
            newlines++;
        }
        while(fgets(data, sizeof(char *)*512*3, pf) != NULL && data[0] != '\0') {
          for(int z = 0; data[z] != '\0'; z++) {
            input[0] = (char_t)data[z];
            input[1] = '\0';
            undoset(INSERT, TRUE);
            insert();
            if(input[0] == '\n')
              newlines++;
          }
        }
        if (curbp->b_point >= curbp->b_epage)
          curbp->b_reframe = 1;
        /* This is ran only for region shell commands, the newlines is
           required to keep the line count correct.
        */
        if(scrap.len > 0) {
          curbp->b_line += newlines;
          currentcommand = KBD_DELETE_WORD;
          backsp();
        }
        if(oscrap != NULL) {
          free(oscrap);
          oscrap = NULL;
        }
        break;
      }
      case 1: {
         data[strlen(data)-1] = '\0';
        /* Find the file name and find the line number */
        if(data[0] == '\0')
          goto do_finish;
        cbuf = strtok(data, ":");
        strcat(temp, cbuf);
        cbuf = strtok(NULL, ":");
        if(cbuf != NULL && (line = atoi(cbuf)) == 0) {
          strcat(temp, ":");
          strcat(temp, cbuf);
        }
        strcat(temp, "\0");
        if(line < 1)
          free(cbuf);
        bp = find_buffer(temp, TRUE);
        disassociate_b(curwp);
        curbp = bp;
        associate_b2w(curbp, curwp);
        if (!growgap(curbp, CHUNK))
          fatal("%s: Failed to allocate required memory.\n");
        movegap(curbp, 0);
        /* load the file if not already loaded */
        if (bp != NULL && bp->b_fname[0] == '\0') {
           if (!load_file(temp)) {
             msg("New file %s", temp);
           }
          strncpy(curbp->b_fname, temp, PATH_MAX);
          curbp->b_fname[PATH_MAX] = '\0'; /* truncate if required */
          if(line > 0) {
            point = line_to_point(line);
            if (point != -1) {
              curbp->b_point = point;
              if (curbp->b_epage < pos(curbp, curbp->b_ebuf))
                curbp->b_reframe = 1;
              msg("Line %d", line);
            } else {
              msg("Line %d, not found", line);
            }
            update_display();
          }
        }
        break;
      }
    }
  }
do_finish:
  /* Some commands, such as ones that copy from region, will mess up
     curbp->b_line due to the cut that is preformed. We want to reset
     that mistake.
  */
  if(curbp->b_line != oline && newlines == 0 && io == 0)
    curbp->b_line = oline;

  if(command != NULL) {
    free(command);
    command = NULL;
  }
  if(data != NULL) {
    free(data);
    data = NULL;
  }
  if (pclose(pf) != 0) {
    msg("Error: Failed to close command stream: %s", strerror(errno));
  }
}
